package model

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"math"
	"time"
	"wxpay/pkg/merrors"
	"wxpay/pkg/util"

	"gorm.io/datatypes"
)

/*
DB Table Details
-------------------------------------


Table: refund
[ 0] id                                             INT8                 null: false  primary: true   isArray: false  auto: false  col: INT8            len: -1      default: []
[ 1] brand_id                                       VARCHAR(16)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 16      default: []
[ 2] mch_id                                         VARCHAR(32)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
[ 3] sub_mch_id                                     VARCHAR(32)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
[ 4] appid                                          VARCHAR(32)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
[ 5] sub_appid                                      VARCHAR(32)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
[ 6] out_trade_no                                   VARCHAR(32)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
[ 7] wx_trans_id                                    VARCHAR(32)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
[ 8] out_refund_no                                  VARCHAR(32)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
[ 9] reason                                         VARCHAR(80)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 80      default: []
[10] amount_info                                    JSONB                null: false  primary: false  isArray: false  auto: false  col: JSONB           len: -1      default: []
[11] refund_id                                      VARCHAR(32)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
[12] channel                                        VARCHAR(16)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 16      default: []
[13] user_received_account                          VARCHAR(64)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 64      default: []
[14] status                                         VARCHAR(16)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 16      default: []
[15] extra                                          JSONB                null: true   primary: false  isArray: false  auto: false  col: JSONB           len: -1      default: []
[16] success_time                                   TIMESTAMPTZ          null: true   primary: false  isArray: false  auto: false  col: TIMESTAMPTZ     len: -1      default: []
[17] create_time                                    TIMESTAMPTZ          null: false  primary: false  isArray: false  auto: false  col: TIMESTAMPTZ     len: -1      default: []
[18] update_time                                    TIMESTAMPTZ          null: false  primary: false  isArray: false  auto: false  col: TIMESTAMPTZ     len: -1      default: []
[19] funds_account                                  VARCHAR(32)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []

JSON Sample
-------------------------------------
{    "appid": "JDQAQlWvcpnTRpvSEwGxxBSBv",    "refund_id": "lkNwyyZXIIThAZbZZyIMqBcAn",    "update_time": "2248-09-04T04:42:14.19515006+08:00",    "id": 53,    "out_trade_no": "TWcOYMXFlWQPfqkmEfbnttClV",    "wx_trans_id": "lEUYNhlmvmSxEqKgFXASKgIHg",    "out_refund_no": "STYPbsYMniDJoJwhRDsYsRoMv",    "reason": "jwrsTQateCFebbdJXdHAdnkiu",    "success_time": "2201-10-21T19:00:43.299733143+08:00",    "create_time": "2033-11-01T16:25:50.747349543+08:00",    "mch_id": "ticlWXKCqyLTjcCEiDvTjqlMA",    "sub_appid": "cmtWXJAySjXEoYXEsTTUcfUki",    "channel": "jJUcqHhZLwPjqFlGRAcEKaejE",    "user_received_account": "UaHFREhMdLDdQeXjVTJjWHvxW",    "status": "PvMePpHUVcYJEZwNBKTAIobRA",    "brand_id": "RcMEeESxHXiTNCGISRXisYvNf",    "sub_mch_id": "MPmbIuskYisIoSNFpOLudZFLQ",    "amount_info": "EWhfrYBMhLpkaivvsBGoQdEGW",    "extra": "vVoNRVauqFXvsLFOLoetBjbvX"}



*/

// Refund struct is a row record of the refund table in the wxpay database
type Refund struct {
	//[ 0] id                                             INT8                 null: false  primary: true   isArray: false  auto: false  col: INT8            len: -1      default: []
	ID int64 `gorm:"primary_key;column:id;type:INT8;"`
	//[ 1] brand_id                                       VARCHAR(16)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 16      default: []
	BrandID string `gorm:"column:brand_id;type:VARCHAR;size:16;"`
	//[ 2] mch_id                                         VARCHAR(32)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
	MchID string `gorm:"column:mch_id;type:VARCHAR;size:32;"`
	//[ 3] sub_mch_id                                     VARCHAR(32)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
	SubMchID string `gorm:"column:sub_mch_id;type:VARCHAR;size:32;"`
	//[ 4] appid                                          VARCHAR(32)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
	AppID string `gorm:"column:appid;type:VARCHAR;size:32;"`
	//[ 5] sub_appid                                      VARCHAR(32)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
	SubAppID sql.NullString `gorm:"column:sub_appid;type:VARCHAR;size:32;"`
	//[ 6] out_trade_no                                   VARCHAR(32)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
	OutTradeNo string `gorm:"column:out_trade_no;type:VARCHAR;size:32;"`
	//[ 7] wx_trans_id                                    VARCHAR(32)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
	WxTransID string `gorm:"column:wx_trans_id;type:VARCHAR;size:32;"`
	//[ 8] out_refund_no                                  VARCHAR(32)          null: false  primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
	OutRefundNo string `gorm:"column:out_refund_no;type:VARCHAR;size:32;"`
	//[ 9] reason                                         VARCHAR(80)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 80      default: []
	Reason sql.NullString `gorm:"column:reason;type:VARCHAR;size:80;"`
	//[10] amount_info                                    JSONB                null: false  primary: false  isArray: false  auto: false  col: JSONB           len: -1      default: []
	AmountInfo datatypes.JSON `gorm:"column:amount_info;type:JSONB;"`
	//[11] refund_id                                      VARCHAR(32)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
	RefundID sql.NullString `gorm:"column:refund_id;type:VARCHAR;size:32;"`
	//[12] channel                                        VARCHAR(16)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 16      default: []
	Channel sql.NullString `gorm:"column:channel;type:VARCHAR;size:16;"`
	//[13] user_received_account                          VARCHAR(64)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 64      default: []
	UserReceivedAccount sql.NullString `gorm:"column:user_received_account;type:VARCHAR;size:64;"`
	//[14] status                                         VARCHAR(16)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 16      default: []
	Status sql.NullString `gorm:"column:status;type:VARCHAR;size:16;"`
	//[15] extra                                          JSONB                null: true   primary: false  isArray: false  auto: false  col: JSONB           len: -1      default: []
	Extra datatypes.JSON `gorm:"column:extra;type:JSONB;"`
	//[16] success_time                                   TIMESTAMPTZ          null: true   primary: false  isArray: false  auto: false  col: TIMESTAMPTZ     len: -1      default: []
	SuccessTime *time.Time `gorm:"column:success_time;type:TIMESTAMPTZ;"`
	//[17] create_time                                    TIMESTAMPTZ          null: false  primary: false  isArray: false  auto: false  col: TIMESTAMPTZ     len: -1      default: []
	CreateTime time.Time `gorm:"column:create_time;type:TIMESTAMPTZ;"`
	//[18] update_time                                    TIMESTAMPTZ          null: false  primary: false  isArray: false  auto: false  col: TIMESTAMPTZ     len: -1      default: []
	UpdateTime time.Time `gorm:"column:update_time;type:TIMESTAMPTZ;"`
	//[19] funds_account                                  VARCHAR(32)          null: true   primary: false  isArray: false  auto: false  col: VARCHAR         len: 32      default: []
	FundsAccount sql.NullString `gorm:"column:funds_account;type:VARCHAR;size:32;"`
}

// TableName sets the insert table name for this struct type
func (r *Refund) TableName() string {
	return "refund"
}

type RefundAmount struct {
	// 退款金额
	Refund uint `json:"refund"`
	// 总金额
	Total uint `json:"total"`
	// 货币类型
	Currency string `json:"currency"`
	// 用户支付金额
	PayerTotal uint `json:"payer_total,omitempty"`
	// 用户退款金额
	PayerRefund uint `json:"payer_refund,omitempty"`
}

func (r *Refund) GetRefundAmount() *RefundAmount {
	info := &RefundAmount{}
	info.ToStruct(r.AmountInfo)
	return info
}

func (info *RefundAmount) ToJson() (r datatypes.JSON) {
	r, _ = json.Marshal(info)
	return
}

func (info *RefundAmount) ToStruct(r datatypes.JSON) {
	b, _ := r.MarshalJSON()
	_ = json.Unmarshal(b, info)
}

const RefundStatusSuccess = "SUCCESS"
const RefundStatusClosed = "CLOSE"
const RefundStatusAbnormal = "ABNORMAL"
const RefundStatusProcessing = "PROCESSING"

// 更新AmountInfo中payer金额属性
func (r *Refund) updatePayerAmount(amount RefundAmount) {
	amountInfo := &RefundAmount{}
	amountInfo.ToStruct(r.AmountInfo)
	amountInfo.PayerRefund = amount.PayerRefund
	r.AmountInfo = amountInfo.ToJson()
}

// 更新到退款成功
func (r *Refund) UpdateToSuccess(successT time.Time, userAccount string, amount RefundAmount, trade *Trade, refunds []*Refund) error {
	const op merrors.Op = "model.Refund.UpdateToSuccess"
	if r.IsFinalState() {
		return merrors.E(op, merrors.Invalid, merrors.ErrStatusInValid, fmt.Sprintf("当前退款单状态:%s, 不能更新到成功", r.Status.String))
	}
	r.Status = util.AdaptNullString(RefundStatusSuccess)
	r.SuccessTime = &successT
	r.UserReceivedAccount = util.AdaptNullString(userAccount)
	r.updatePayerAmount(amount)
	// 计算退款需要退回的手续费
	var refundedAmount int64
	var refundedServiceFee int64
	for _, r := range refunds {
		refundedAmount += int64(r.GetRefundAmount().Refund)
		refundedServiceFee += r.GetServiceFeeReturn()
	}
	returnFee := r.CalculateServiceFeeReturn(trade.GetTotal(), *trade.GetServiceFee(), refundedAmount, refundedServiceFee)
	r.Extra = NewRefundExtra(returnFee).ToJson()
	return nil
}

// 更新到退款关闭
func (r *Refund) UpdateToClosed(userAccount string) error {
	const op merrors.Op = "model.Refund.UpdateToClosed"
	if r.IsFinalState() {
		return merrors.E(op, merrors.Invalid, merrors.ErrStatusInValid, fmt.Sprintf("当前退款单状态:%s, 不能更新到关闭", r.Status.String))
	}
	r.Status = util.AdaptNullString(RefundStatusClosed)
	r.UserReceivedAccount = util.AdaptNullString(userAccount)
	return nil
}

// 更新到退款处理中
func (r *Refund) UpdateToProcessing(userAccount string) error {
	const op merrors.Op = "model.Refund.UpdateToProcessing"
	if r.IsFinalState() {
		return merrors.E(op, merrors.Invalid, merrors.ErrStatusInValid, fmt.Sprintf("当前退款单状态:%s, 不能更新到关闭", r.Status.String))
	}
	r.Status = util.AdaptNullString(RefundStatusProcessing)
	r.UserReceivedAccount = util.AdaptNullString(userAccount)
	return nil
}

// 更新到退款异常
func (r *Refund) UpdateToAbnormal(userAccount string) error {
	const op merrors.Op = "model.Refund.UpdateToAbnormal"
	if r.IsFinalState() {
		return merrors.E(op, merrors.Invalid, merrors.ErrStatusInValid, fmt.Sprintf("当前退款单状态:%s, 不能更新到退款异常", r.Status.String))
	}
	r.Status = util.AdaptNullString(RefundStatusAbnormal)
	r.UserReceivedAccount = util.AdaptNullString(userAccount)
	return nil
}

// 是否终态判断
func (r *Refund) IsFinalState() bool {
	switch util.ConvertNullString(r.Status) {
	case "SUCCESS", "CLOSE", "ABNORMAL":
		return true
	}
	return false
}

type refundExtra struct {
	// 退回手续费
	ServiceFeeReturn int64 `json:"service_fee_return"`
}

func NewRefundExtra(fee int64) *refundExtra {
	return &refundExtra{
		ServiceFeeReturn: fee,
	}
}

func (info *refundExtra) ToJson() (r datatypes.JSON) {
	r, _ = json.Marshal(info)
	return
}

func (info *refundExtra) ToStruct(r datatypes.JSON) {
	b, _ := r.MarshalJSON()
	_ = json.Unmarshal(b, info)
}

// 退回服务费
func (t *Refund) GetServiceFeeReturn() int64 {
	extra := t.GetExtra()
	return extra.ServiceFeeReturn
}

// 退回服务费 round((本次结算额)/(已结算金额（交易金额）- 已退款金额) * (已收手续费（交易）- 已退手续费))
func (t *Refund) CalculateServiceFeeReturn(tradeAmount int64, tradeServiceFee int64, refundedAmount int64, refundedServiceFee int64) int64 {
	// 结算金额 = 本次退款金额
	amount := int64(t.GetRefundAmount().Refund)
	return int64(math.Round(float64(amount*(tradeServiceFee-refundedServiceFee)) / float64(tradeAmount-refundedAmount)))
}

func (t *Refund) GetExtra() refundExtra {
	extra := refundExtra{}
	extra.ToStruct(t.Extra)
	return extra
}
